# Licensed to Cloudera, Inc. under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  Cloudera, Inc. licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Copyright (c) 2015 Cloudera, Inc. All rights reserved.

import logging
import time

from cmf.monitor import schema
from cmf.monitor.abstract_monitor import CollectionError
from cmf.monitor.constants import WEB_METRICS_COLLECTION_GOOD, \
  DEFAULT_MONITOR_LOG_RATE, WEB_METRICS_COLLECTION_COMMUNICATION_FAILURE
from cmf.monitor.generic import AbstractMetricCollector
from cmf.monitor.generic.adapter import Adapter
from cmf.monitor.generic.service_defined_metrics import ServiceDefinedMetrics
from cmf.monitor.generic.utils import SplittingSourceProcessor, visit_json, \
  SimpleMetricsExtractor
from cmf.throttling_logger import ThrottlingLogger
from url_util import head_request_with_timeout

logging.basicConfig()
LOG = logging.getLogger('HueServerAdapter')
THROTTLED_LOG = ThrottlingLogger(LOG, DEFAULT_MONITOR_LOG_RATE)

class _HueIsAliveCollector(AbstractMetricCollector):
  """
  A collector that generates web-metric collection metrics from the hue is alive
  end-point.
  """

  def __init__(self, adapter):
    AbstractMetricCollector.__init__(self, adapter)
    if not isinstance(adapter, HueServerAdapter):
      raise Exception("Unsupported adapter type")

  def update_with_conf(self, conf):
    LOG.info("hue_adapters: _HueIsAliveCollector: update_with_conf: metrics_uri: %s" % self._adapter._get_is_alive_url(conf))
    self._metrics_uri = self._adapter._get_is_alive_url(conf)

  def collect_and_parse(self, conf):
    start = time.time()
    try:
      LOG.info("hue_adapters: _HueIsAliveCollector: _call_is_alive: metrics_uri: %s" % self._metrics_uri)
      self._call_is_alive(self._metrics_uri)
      result = WEB_METRICS_COLLECTION_GOOD
    except Exception, ex:
      THROTTLED_LOG.exception("Error calling is alive at '%s'" %
                              (self._metrics_uri,))
      result = CollectionError(WEB_METRICS_COLLECTION_COMMUNICATION_FAILURE)
    now = time.time()
    self._metrics_tuple = result, now, int((now - start) * 1000)

  def _add_metrics(self, service_version, update, collected_metrics, duration):
    update.add_metric(schema.WEB_METRICS_COLLECTION_DURATION, duration)
    if isinstance(collected_metrics, CollectionError):
      update.add_metric(schema.WEB_METRICS_COLLECTION_STATUS,
                        collected_metrics.code)
    else:
      update.add_metric(schema.WEB_METRICS_COLLECTION_STATUS,
                        collected_metrics)
    return True

  def is_ready_to_report(self, conf, pid):
    if pid is None:
      raise Exception("Pid cannot be None")

    metrics_url = self._adapter._get_is_alive_url(conf)
    if metrics_url is None:
      return AbstractMetricCollector._COLLECTOR_NOT_SUPPORTED
    try:
      self._call_is_alive(metrics_url, 0.5)
      return True
    except Exception, ex:
      pass
    return False

  def _call_is_alive(self, is_alive_url, timeout=None):
    """
    We need this function to make testing eaiser.
    """
    head_request_with_timeout(is_alive_url, timeout=timeout)

class HueServerAdapter(Adapter):
  """
  An adapter that collects and generates metrics for the HUE_SERVER. The adapter
  collects metrics from a sample file generated by hue. It also generates
  web-metric-collection metrics using an is-alive endpoint exposed by hue.
  Note that the web-metric-collection metrics generation is done as part of the
  sample file metric collection. The reason is that the hue is-alive end-point
  accepts HTTP HEAD requests and we can't use the regular web metric collector
  to generate these metics.
  """

  _HUE_SERVER_METRICS_SAMPLE_FILE_KEY = "location"
  _HUE_SERVER_HTTP_HOST_KEY = "http_host"
  _HUE_SERVER_HTTP_PORT_KEY = "http_port"
  _HUE_SERVER_SSL_ENABLED_KEY = "ssl_enable"
  _SERVICE_RELEASE = 'service_release'

  def __init__(self, safety_valve):
    Adapter.__init__(self, "HUE", "HUE_SERVER", safety_valve)
    self._metrics = None
    self._is_alive_collector = _HueIsAliveCollector(self)

  def read_service_defined_metrics(self, path):
    if path is None:
      raise Exception("A path is required!")
    self._metrics = ServiceDefinedMetrics(path, SplittingSourceProcessor('::'))

  def get_metrics_file(self, conf):
    if conf is None:
      raise Exception("A configuration is required!")

    try:
      return conf.get(
        self.section,
        HueServerAdapter._HUE_SERVER_METRICS_SAMPLE_FILE_KEY)
    except:
      LOG.exception("Failed to retrieve %s from monitoring configuration file" %
                    HueServerAdapter._HUE_SERVER_METRICS_SAMPLE_FILE_KEY)

  def _get_is_alive_url(self, conf):
    if conf is None:
      raise Exception("A configuration is required!")

    if not self._is_alive_supported(conf):
      return None

    try:
      host = conf.get(self.section, HueServerAdapter._HUE_SERVER_HTTP_HOST_KEY)
      if host is None:
        LOG.error("%s entry missing from monitoring configuration file" %
                  (HueServerAdapter._HUE_SERVER_HTTP_HOST_KEY, ))
        return None
      port = conf.getint(self.section,
                         HueServerAdapter._HUE_SERVER_HTTP_PORT_KEY)
      if port is None:
        LOG.error("%s entry missing from monitoring configuration file" %
                  (HueServerAdapter._HUE_SERVER_HTTP_PORT_KEY, ))
        return None
      if conf.getboolean_with_default(
          self.section,
          HueServerAdapter._HUE_SERVER_SSL_ENABLED_KEY,
          False):
        url_format = "https://%s:%s/desktop/debug/is_alive"
      else:
        url_format = "http://%s:%s/desktop/debug/is_alive"
      return url_format % (host, port)
    except:
      LOG.exception("Failed to read monitoring configuration file")
      return None

  def parse_metrics_from_file(self, conf, json):
    if json is None:
      raise Exception("a json sample is required")
    if self._metrics is None:
      raise Exception("No metrics have been loaded!")

    role_extractor = SimpleMetricsExtractor(
      self._metrics.get_sources(self._role_type))
    visit_json(json, [role_extractor])
    return role_extractor.metrics

  def add_sample_file_metrics(self, version, update, metrics, accessors):
    for metric_id, value in metrics.iteritems():
      update.add_metric(metric_id, value)

  def get_metrics_sample(self, version):
    return _HUE_SERVER_METRICS_SAMPLE

  def _is_alive_supported(self, conf):
    return True

  def get_adapter_specific_collectors(self):
    return [self._is_alive_collector]

_HUE_SERVER_METRICS_SAMPLE="""
{
  "desktop.auth.oauth.authentication-time": {
    "1m_rate": 0,
    "999_percentile": 0,
    "15m_rate": 0,
    "99_percentile": 1234567,
    "mean_rate": 0,
    "max": 0,
    "sum": 0,
    "min": 0,
    "5m_rate": 0,
    "count": 0,
    "75_percentile": 0,
    "std_dev": 0,
    "95_percentile": 0,
    "avg": 0
  },
  "desktop.auth.saml2.authentication-time": {
    "1m_rate": 0,
    "999_percentile": 0,
    "15m_rate": 0,
    "99_percentile": 0,
    "mean_rate": 0,
    "max": 0,
    "sum": 0,
    "min": 0,
    "5m_rate": 0,
    "count": 0,
    "75_percentile": 0,
    "std_dev": 0,
    "95_percentile": 0,
    "avg": 0
  },
  "python.threads.count": {
    "value": 42.5
  },
  "desktop.users.logged-in.count": {
    "count": 1
  },
  "python.gc.referrers.count": {
    "value": 0
  },
  "desktop.requests.exceptions.count": {
    "count": 7
  },
  "python.multiprocessing.active": {
    "value": 0
  },
  "desktop.users.count": {
    "value": 2
  },
  "python.gc.referents.count": {
    "value": 0
  },
  "python.threads.active": {
    "value": 52
  },
  "python.threads.daemon": {
    "value": 1
  },
  "desktop.auth.pam.authentication-time": {
    "1m_rate": 0,
    "999_percentile": 0,
    "15m_rate": 0,
    "99_percentile": 0,
    "mean_rate": 0,
    "max": 0,
    "sum": 0,
    "min": 0,
    "5m_rate": 0,
    "count": 0,
    "75_percentile": 0,
    "std_dev": 0,
    "95_percentile": 0,
    "avg": 0
  },
  "desktop.auth.spnego.authentication-time": {
    "1m_rate": 0,
    "999_percentile": 0,
    "15m_rate": 0,
    "99_percentile": 0,
    "mean_rate": 0,
    "max": 0,
    "sum": 0,
    "min": 0,
    "5m_rate": 0,
    "count": 0,
    "75_percentile": 0,
    "std_dev": 0,
    "95_percentile": 0,
    "avg": 0
  },
  "desktop.auth.ldap.authentication-time": {
    "1m_rate": 0,
    "999_percentile": 0,
    "15m_rate": 0,
    "99_percentile": 0,
    "mean_rate": 0,
    "max": 0,
    "sum": 0,
    "min": 0,
    "5m_rate": 0,
    "count": 0,
    "75_percentile": 0,
    "std_dev": 0,
    "95_percentile": 0,
    "avg": 0
  },
  "desktop.requests.aggregate-response-time": {
    "1m_rate": 2.5683487079247715e-101,
    "999_percentile": 11.331326007843018,
    "15m_rate": 4.33344429167876e-8,
    "99_percentile": 11.331326007843018,
    "mean_rate": 0.0005057717284166405,
    "max": 11.331326007843018,
    "sum": 15.07522988319397,
    "min": 0.007016897201538086,
    "5m_rate": 2.0635466335636803e-21,
    "count": 7,
    "75_percentile": 2.34737491607666,
    "std_dev": 4.1352759732033935,
    "95_percentile": 11.331326007843018,
    "avg": 2.15360426902771
  },
  "python.gc.objects.count": {
    "value": 218408
  },
  "python.multiprocessing.count": {
    "value": 0
  },
  "desktop.auth.openid.authentication-time": {
    "1m_rate": 0,
    "999_percentile": 0,
    "15m_rate": 0,
    "99_percentile": 0,
    "mean_rate": 0,
    "max": 0,
    "sum": 0,
    "min": 0,
    "5m_rate": 0,
    "count": 0,
    "75_percentile": 0,
    "std_dev": 0,
    "95_percentile": 0,
    "avg": 0
  },
  "python.gc.collection.count2": {
    "value": 10
  },
  "python.gc.collection.count0": {
    "value": 10
  },
  "python.gc.collection.count1": {
    "value": 10
  },
  "python.multiprocessing.daemon": {
    "value": 0
  },
  "desktop.requests.active.count": {
    "count": 0
  }
}
"""
